<!--
    @Title: index
    @Author  dudalei
    @Package
    @Date  2024/11/30 上午10:50
    @description:
-->
<script setup lang="ts">
import Stats from 'stats.js'
import {
  Components,
  FragmentsManager,
  Grids,
  IfcGeometryTiler,
  IfcLoader,
  IfcPropertiesTiler,
  IfcPropertiesUtils,
  IfcRelationsIndexer,
  MeasurementUtils,
  Raycasters,
  SimpleCamera,
  SimpleRenderer,
  SimpleScene,
  type StreamedAsset,
  type StreamedGeometries,
  Worlds,
} from '@thatopen/components'
import { onMounted, useTemplateRef, ref } from 'vue'
import { EdgesGeometry, LineBasicMaterial, LineSegments, Mesh, type Vector3 } from 'three'
import { IFCREINFORCINGBAR, IFCREINFORCINGELEMENT, IFCTENDONANCHOR } from 'web-ifc'
import ifcUrl from '@/assets/ifc/all-1.ifc?url'
// import ifcUrl from '@/assets/ifc/rac_advanced_sample_project.ifc?url'
import smallFragUrl from '@/assets/ifc/small.frag?url'
import smallJsonUrl from '@/assets/ifc/small.json?url'

import { Highlighter } from '@thatopen/components-front'

const fileName = ifcUrl.substring(ifcUrl.lastIndexOf('/') + 1).replace('?', '')

const container = useTemplateRef<HTMLHtmlElement>('containerRef')

const components = new Components()

const worlds = components.get(Worlds)

const world = worlds.create<SimpleScene, SimpleCamera, SimpleRenderer>()

function init() {
  if (!container.value) {
    return
  }

  world.scene = new SimpleScene(components)
  world.renderer = new SimpleRenderer(components, container.value)
  world.camera = new SimpleCamera(components)

  components.init()

  world.scene.setup()

  world.scene.three.background = null

  /*  const material = new MeshLambertMaterial({ color: '#6528D7' })
  const geometry = new BoxGeometry()
  const cube = new Mesh(geometry, material)
  world.scene.three.add(cube)*/

  world.camera.controls.setLookAt(3, 3, 3, 0, 0, 0)

  const stats = new Stats()
  stats.showPanel(2)
  container.value.append(stats.dom)
  stats.dom.style.left = '0px'
  stats.dom.style.zIndex = 'unset'
  world.renderer.onBeforeUpdate.add(() => stats.begin())
  world.renderer.onAfterUpdate.add(() => stats.end())

  const grids = components.get(Grids)
  grids.create(world)
  // loadIFC()
}

async function delay(time = 1000) {
  return new Promise((resolve) => {
    setTimeout(resolve, time)
  })
}

/**
 * 下载文件
 * @param name
 * @param bits
 */
function downloadFile(name: string, ...bits: (Uint8Array | string)[]) {
  const file = new File(bits, name)
  const anchor = document.createElement('a')
  const url = URL.createObjectURL(file)
  anchor.href = url
  anchor.download = file.name
  anchor.click()
  URL.revokeObjectURL(url)
}

/**
 * 按顺序下载文件
 * @param fileList
 */
async function downloadFilesSequentially(
  fileList: { name: string; bits: (Uint8Array | string)[] }[],
) {
  for (const { name, bits } of fileList) {
    if (Array.isArray(bits)) {
      downloadFile(name, ...bits)
    } else {
      downloadFile(name, bits)
    }

    await delay(100)
  }
}

let tiler: IfcGeometryTiler
// 是否导出切片 注意切片和属性需要分开导出,不然会有缺失
const exportGeo = true
// 是否导出属性
const exportProp = false
if (exportGeo) {
  tiler = components.get(IfcGeometryTiler)

  const wasm = {
    path: 'https://unpkg.com/web-ifc@0.0.66/',
    absolute: true,
  }

  tiler.settings.wasm = wasm
  tiler.settings.minGeometrySize = 20
  tiler.settings.minAssetsSize = 1000
  let files: { name: string; bits: (Uint8Array | string)[] }[] = []
  let geometriesData: StreamedGeometries = {}
  let geometryFilesCount = 1

  tiler.onGeometryStreamed.add((geometry) => {
    const { buffer, data } = geometry
    const bufferFileName = `${fileName}-processed-geometries-${geometryFilesCount}`
    for (const expressID in data) {
      const value = data[expressID]
      value.geometryFile = bufferFileName
      geometriesData[expressID] = value
    }
    files.push({ name: bufferFileName, bits: [buffer] })
    console.log('geometryFilesCount', geometryFilesCount)
    geometryFilesCount++
  })
  let assetsData: StreamedAsset[] = []

  tiler.onAssetStreamed.add((assets) => {
    assetsData = [...assetsData, ...assets]
  })
  tiler.onIfcLoaded.add((groupBuffer) => {
    files.push({
      name: `${fileName}-processed-global`,
      bits: [groupBuffer],
    })
  })

  tiler.onProgress.add((progress) => {
    if (progress !== 1) return

    setTimeout(async () => {
      const processedData = {
        geometries: geometriesData,
        assets: assetsData,
        globalDataFileId: `${fileName}-processed-global`,
      }
      files.push({
        name: `${fileName}-processed.json`,
        bits: [JSON.stringify(processedData)],
      })

      await downloadFilesSequentially(files)
      assetsData = []
      geometriesData = {}
      files = []
      geometryFilesCount = 1
    }, 10)
  })
}

let propsStreamer: IfcPropertiesTiler
if (exportProp) {
  propsStreamer = components.get(IfcPropertiesTiler)

  propsStreamer.settings.wasm = {
    path: 'https://unpkg.com/web-ifc@0.0.66/',
    absolute: true,
  }
  interface StreamedProperties {
    types: {
      [typeID: number]: number[]
    }

    ids: {
      [id: number]: number
    }

    indexesFile: string
  }
  const jsonFile: StreamedProperties = {
    types: {},
    ids: {},
    indexesFile: `${fileName}-processed-properties-indexes`,
  }
  let counter = 0

  const files: { name: string; bits: Blob }[] = []

  propsStreamer.onPropertiesStreamed.add(async (props) => {
    if (!jsonFile.types[props.type]) {
      jsonFile.types[props.type] = []
    }
    jsonFile.types[props.type].push(counter)

    for (const id in props.data) {
      jsonFile.ids[id] = counter
    }

    const name = `${fileName}-processed-properties-${counter}`
    const bits = new Blob([JSON.stringify(props.data)])
    files.push({ bits, name })

    counter++
  })

  propsStreamer.onProgress.add(async (v) => {
    // console.log(progress)
    // progress.value = v
  })

  propsStreamer.onIndicesStreamed.add(async (props) => {
    files.push({
      name: `${fileName}-processed-properties.json`,
      bits: new Blob([JSON.stringify(jsonFile)]),
    })

    const relations = components.get(IfcRelationsIndexer)
    const serializedRels = relations.serializeRelations(props)

    files.push({
      name: `${fileName}-processed-properties-indexes`,
      bits: new Blob([serializedRels]),
    })

    await downloadFilesSequentially(files)
  })
}

const fragments = components.get(FragmentsManager)

async function loadIFC() {
  console.log('loadIFC')

  const fragmentIfcLoader = components.get(IfcLoader)
  // debugger
  // fragmentIfcLoader.settings.wasm = {
  //   path: '/web-ifc/',
  //   absolute: true,
  // }
  await fragmentIfcLoader.setup({
    // autoSetWasm: false,
  })
  // 排除我们不想转换为 fragment 的类别
  const excludedCats = [IFCTENDONANCHOR, IFCREINFORCINGBAR, IFCREINFORCINGELEMENT]

  for (const cat of excludedCats) {
    fragmentIfcLoader.settings.excludedCategories.add(cat)
  }
  fragmentIfcLoader.settings.webIfc.COORDINATE_TO_ORIGIN = true

  // const file = await fetch(ifcUrl)
  // http://localhost:8081/all.ifc
  const file = await fetch(ifcUrl)
  /*
  // 分段获取
  const reader = file.body!.getReader()

  let result = true
  const myArrays = []
  while (result) {
    const { done, value } = await reader.read()
    if (done) {
      console.log('Stream ended')
      result = false
      break
    }
    myArrays.push(value)
    // console.log(value)
  }
  let length = 0

  myArrays.forEach((item) => {
    length += item.length
  })

  // Create a new array with total length and merge all source arrays.
  const mergedArray = new Uint8Array(length)
  let offset = 0
  myArrays.forEach((item) => {
    mergedArray.set(item, offset)
    offset += item.length
  })
 */

  const data = await file.arrayBuffer()
  const buffer = new Uint8Array(data)
  const model = await fragmentIfcLoader.load(buffer)
  // model.name = 'example'
  model.scale.set(0.2, 0.2, 0.2)
  world.scene.three.add(model)
  // select(model)
  // This triggers the conversion, so the listeners start to be called
  if (tiler) {
    tiler.streamFromBuffer(buffer)
    // return
  }

  // This triggers the conversion, so the listeners start to be called
  if (propsStreamer) {
    propsStreamer.streamFromBuffer(buffer)
  }
}

function download(file: File) {
  const link = document.createElement('a')
  link.href = URL.createObjectURL(file)
  link.download = file.name
  document.body.appendChild(link)
  link.click()
  link.remove()
}

async function exportFragments() {
  if (!fragments.groups.size) {
    return
  }
  const group = Array.from(fragments.groups.values())[0]
  const data = fragments.export(group)
  download(new File([new Blob([data])], 'small.frag'))

  const properties = group.getLocalProperties()
  if (properties) {
    download(new File([JSON.stringify(properties)], 'small.json'))
  }
}

async function select(model: any) {
  const highlighter = components.get(Highlighter)
  highlighter.setup({ world })
  // 选择模型后可以自动缩放到选择区域
  highlighter.zoomToSelection = true
  // 一旦模型加载到内存中，你只需要获取 IfcRelationsIndexer 的实例并处理模型......就这么简单！😎

  const indexer = components.get(IfcRelationsIndexer)
  await indexer.process(model)

  highlighter.events.select.onHighlight.add((fragmentIdMap) => {
    Object.values(fragmentIdMap).forEach(async (item: Set<number>, index) => {
      if (index > 0) {
        return
      }
      for (const id of item) {
        const psets = indexer.getEntityRelations(model, id, 'IsDefinedBy')
        if (psets) {
          for (const expressID of psets) {
            // You can get the pset attributes like this
            const pset = await model.getProperties(expressID)
            console.log('pset', pset)
            // You can get the pset props like this or iterate over pset.HasProperties yourself
            await IfcPropertiesUtils.getPsetProps(model, expressID, async (propExpressID) => {
              const prop = await model.getProperties(propExpressID)
              console.log('prop', prop)
            })
          }
        }

        /*  const pset = await model.getProperties(id)

        const pset2 = await model.getProperties(id)

        console.log('pset', pset)
        console.log('pset2', pset2)
        await IfcPropertiesUtils.getPsetProps(model, 4963492, async (propExpressID) => {
          const prop = await model.getProperties(propExpressID)
          console.log('prop', prop)
        })*/
        // debugger
      }
    })
  })

  highlighter.events.select.onClear.add(() => {
    console.log('clear')
  })
}

async function loadM() {
  const fragFile = await fetch(smallFragUrl)
  const fragData = await fragFile.arrayBuffer()
  const fragBuffer = new Uint8Array(fragData)
  const model = fragments.load(fragBuffer)
  world.scene.three.add(model)
  const fragJson = await fetch(smallJsonUrl)
  const jsonData = await fragJson.json()

  model.setLocalProperties(jsonData)
  select(model)
  // addEvent(model)
  /* MD
  :::tip

  The `elementProperties` functional component is a simplified version that shows any model entity data. However, if you like a more complete properties table, use the `entityAttributes` component.

  :::

  Cool! properties table created. Then after, let's tell the properties table to update each time the user makes a selection over the model. For it, we will use the highlighter from `@thatopen/components-front`:
  */
}

function addEvent(model: any) {
  if (!world.renderer) {
    return false
  }
  // 我们将获取人脸信息，并在用户将鼠标悬停在 BIM 模型上时，在我们刚刚加载的 BIM 模型上以 3D 形式表示它。因此，首先我们需要创建将显示在悬停面顶部的 3D 对象。使用 three.js 非常简单：
  const edges = new EdgesGeometry()
  const material = new LineBasicMaterial({
    color: 0xff0000,
    depthTest: false,
  })
  const line = new LineSegments(edges, material)
  world.scene.three.add(line)
  //现在，为了能够让用户将鼠标悬停在几何体上，检测人脸并获取其 ifnormation，我们需要导入 2 个组件：测量 utils 和 casters。我们将在当前世界中创建一个新的 Caster。

  const measurements = components.get(MeasurementUtils)
  const casters = components.get(Raycasters)
  const caster = casters.get(world)
  const canvas = world.renderer.three.domElement
  canvas.addEventListener('pointermove', () => {
    const result = caster.castRay([model])

    if (!result) return
    if (!(result.object instanceof Mesh)) return
    if (result.faceIndex === undefined) return

    const face = measurements.getFace(result.object, result.faceIndex, result.instanceId)

    if (face) {
      const points: Vector3[] = []
      for (const edge of face.edges) {
        points.push(...edge.points)
      }
      edges.setFromPoints(points)
    }
  })
}

onMounted(init)
</script>

<template>
  <div ref="containerRef" class="container"></div>
  <el-button @click="loadIFC" type="primary" style="position: absolute; top: 20px; right: 20px">
    1. 加载ifc
  </el-button>

  <el-button
    @click="exportFragments"
    type="primary"
    style="position: absolute; top: 60px; right: 20px"
  >
    2. 下载Fragments
  </el-button>
  <el-button @click="loadM" type="primary" style="position: absolute; top: 100px; right: 20px">
    3.加载Fragments
  </el-button>
</template>

<style scoped>
.container {
  width: 100vw;
  height: 100vh;
}
</style>
